package com.mssh.login.core;

import com.alibaba.fastjson.JSONObject;
import com.mssh.entity.pojo.Result;
import com.mssh.login.config.AuthConfig;
import com.mssh.login.util.JwtUtil;
import com.mssh.login.util.SecurityUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import org.springframework.data.redis.core.RedisTemplate;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: 熳殊沙华
 * @Date: 2025/03/25/12:17
 * @Description:
 */
public class JwtFilter extends AuthorizationFilter {

    private final static Logger logger = LogManager.getLogger(JwtFilter.class);

    private RedisTemplate redisTemplate;

    private AuthConfig authConfig;

    /**
     * 构造注入
     * @param redisTemplate
     */
    public JwtFilter(RedisTemplate redisTemplate, AuthConfig authConfig){
        this.redisTemplate = redisTemplate;
        this.authConfig = authConfig;
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        // 放行OPTIONS请求
        if (((HttpServletRequest) request).getMethod().equalsIgnoreCase("OPTIONS")) {
            return true;
        }
        return false;
    }

    /**
     * 重写权限验证不通过时的方法
     * isAccessAllowed返回false时掉用
     * 请求过滤的回调方法
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        String account = "";
        //验证token
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("token");
        // 1. 校验Token基础格式
        if (StringUtils.isEmpty(token)) {
            logger.warn("无效的Token格式！");
            sendError(response, 401, "无效的Token格式");
            return false;
        }
        try{
            // 2、解析token
            Claims claims = JwtUtil.parseToken(token);
            account = claims.getSubject();
            Date expiration = claims.getExpiration();
            // 3. 检查Redis中是否存在有效Token
            String redisKey = "token:user:" + account;
            String voucher = (String) redisTemplate.opsForValue().get(redisKey);
            if (StringUtils.isBlank(voucher) || !token.equals(voucher)) {
                logger.warn("账号[{}]，Token已失效！", account);
                sendError(response, 401, "凭证已过期，请重新登录！");
                return false;
            }

            // 4. 计算剩余时间并触发续期
            long remainingTime = expiration.getTime() - System.currentTimeMillis();
            if (remainingTime <= authConfig.getRefresh() * 1000) {
                refreshToken(response, account, token);
            }
            return true;
        }catch (ExpiredJwtException e) {
            logger.warn("账号[{}]，Token已过期！", account, e);
            sendError(response, 401, "凭证已过期，请重新登录！");
            return false;
        } catch (JwtException | IllegalArgumentException e) {
            logger.warn("账号[{}]，非法Token！", account, e);
            sendError(response, 401, "登录凭证无效！");
            return false;
        }
    }

    /**
     * Token续期操作
     */
    private void refreshToken(ServletResponse response, String userId, String oldToken) {
        // 生成新Token
        String salt = SecurityUtil.generateSalt();
        String newToken = JwtUtil.generateToken(userId, salt);

        //更新Redis并设置新过期时间
        String redisKey = "token:" + userId;
        redisTemplate.opsForValue().set(
                redisKey,
                newToken,
                authConfig.getExpire(),
                TimeUnit.SECONDS
        );

        // 将新Token写入响应头
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setHeader("New-Token", newToken);
    }

    /**
     * 异常返回
     * @param response
     * @param code
     * @param message
     * @throws IOException
     */
    private void sendError(ServletResponse response, int code, String message) throws IOException {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setContentType("application/json;charset=UTF-8");
        httpResponse.setStatus(code);
        Result result = new Result();
        httpResponse.getWriter().write(JSONObject.toJSONString(result.failed(Integer.toString(code), message)));
    }
}

